import logging
import os
import sys
import inspect
import re
import traceback
import time
from datetime import datetime
from configparser import ConfigParser
from multiprocessing import Queue
from pathlib import *
import ctypes
import subprocess
import zipfile
from math import radians, cos, sin, asin, sqrt
import asyncio


g_debug_mode = False


class MyCRC32():
    def __init__(self, root=0x04C11DB7):
        self.root = ctypes.c_uint32(root)
        self.crc32_table = [None] * 256
        self.val = ctypes.c_uint32(0)
        crc = ctypes.c_uint32(0)
        temp = ctypes.c_uint32(0)
        flag = ctypes.c_uint32(0)
        t1 = ctypes.c_uint32(0)
        for i in range(0, 256):
            temp.value = self.reflect(i, 8)
            self.crc32_table[i] = (temp.value << 24)
            for j in range(0, 8):
                flag.value = self.crc32_table[i] & 0x80000000
                t1.value = self.crc32_table[i] << 1
                if flag.value == 0:
                    self.crc32_table[i] = t1.value ^ 0x00000000
                else:
                    self.crc32_table[i] = t1.value ^ self.root.value
            self.crc32_table[i] = self.reflect(self.crc32_table[i], 32)

    def reflect(self, old=0xffffffff, ch=8):
        old_val = ctypes.c_uint32(old)
        val = ctypes.c_uint32(0)
        ch2 = ch + 1
        for i in range(1, ch2):
            if old_val.value & 0x00000001:
                val.value |= 1 << (ch - i)
            old_val.value >>= 1
        return val.value

    def reset(self, start=0xffffffff):
        self.val.value = start

    def cal(self, data):
        for i in range(0, len(data)):
            self.val.value = self.crc32_table[(self.val.value ^ data[i]) & 0xff] ^ (self.val.value >> 8)
        return self.val.value


class MyTaskBase():
    def __init__(self, global_config, logger=None, callback_fun=None, loop=None):
        self.quit_hook = None
        self.event_loop = loop
        self.global_config = global_config
        self.aio_queue = asyncio.Queue(loop=loop)
        self.task_run_flag = True
        if logger:
            self.logger = logger
        else:
            self.logger = dummy_logger
        if callback_fun:
            self.callback_fun = callback_fun
        else:
            self.callback_fun = self.dummy_callback

    async def wait_message_core(self, target=None, timeout=None):
        if not timeout:
            if self.aio_queue.empty():
                await asyncio.sleep(0)
                return None
            else:
                message = self.aio_queue.get_nowait()
                self.aio_queue.task_done()
                await asyncio.sleep(0)
                return message
        end_time = timeout + time.time()
        wait_time = end_time - time.time()
        while wait_time > 0:
            try:
                message = await asyncio.wait_for(self.aio_queue.get(), wait_time)
                self.aio_queue.task_done()
                if target:
                    if message[0] in list(target):
                        return message
                    else:
                        self.message_callback(message)
                else:
                    return message
            except asyncio.TimeoutError:
                return None
            wait_time = end_time - time.time()
        return None

    async def wait_message(self, target=None, timeout=None):
        result = await self.wait_message_core(target, timeout)
        if self.logger and result:
            self.logger.info("{}".format(result))

    def send_message(self, event=None, value=None):
        # self.queue.put_nowait([self.aio_queue, [event, value]])
        self.event_loop.call_soon_threadsafe(self.aio_queue.put_nowait, [event, value])

    def task_stop(self):
        self.task_run_flag = False
        self.send_message()

    def dummy_callback(self, *args, **kwargs):
        pass

    def dummy_logger(self, string):
        print(string)

    def message_callback(self, message):
        print(message)


class MyConfigParser(ConfigParser):
    def __init__(self, defaults=None):
        ConfigParser.__init__(self, defaults=None)

    def optionxform(self, optionstr):
        return optionstr

    def read(self, filename, encoding=None):
        if encoding:
            return super().read(filename, encoding)
        else:
            try:
                return super().read(filename, "gbk")
            except Exception as identifier:
                return super().read(filename, "utf-8")


class MyloggerHandle(logging.Handler):
    def __init__(self, log_file_path):
        logging.Handler.__init__(self)
        self.out = ""
        self.log_file_path = log_file_path
        if not os.path.exists(self.log_file_path):
            with open(self.log_file_path, "w") as f:
                pass

    def flush(self):
        """
        Flushes the stream.
        """

    def emit(self, record):
        """
        Emit a record.

        If a formatter is specified, it is used to format the record.
        The record is then written to the stream with a trailing newline.  If
        exception information is present, it is formatted using
        traceback.print_exception and appended to the stream.  If the stream
        has an 'encoding' attribute, it is used to determine how to do the
        output to the stream.
        """
        try:
            self.out = self.format(record)
            with open(self.log_file_path, "a") as f:
                f.write(self.out + "\n")
        except RecursionError:  # See issue 36272
            raise
        except Exception:
            # self.handleError(record)
            pass


# 所搜指定目录下的所有文件和文件夹，但是不包括子文件夹下的文件
def get_file_from_path(path, suffix=None):
    file_list = []
    if suffix:
        if type(suffix) is list or type(suffix) is dict:
            _check = suffix
        else:
            _check = [suffix]

    if os.path.exists(path):
        for file_path in os.listdir(path):
            file_name, file_suffix = os.path.splitext(file_path)
            if suffix:
                if file_suffix[1:] in _check or file_suffix in _check:
                    file_list.append(file_path)
            else:
                file_list.append(file_path)
    return file_list


def resource_path(relative_path):
    if getattr(sys, 'frozen', False):
        base_path = sys._MEIPASS
    else:
        base_path = os.getcwd()
    return str(Path(base_path, relative_path))


def encode_ch(string, d='gbk'):
    if type(string) is not str:
        return string
    try:
        return string.encode(d)
    except Exception as Error1:
        try:
            if d == 'gbk':
                return string.encode('utf-8')
            else:
                return string.encode('gbk')
        except Exception as Error2:
            return string.encode(d, errors='ignore')


def decode_ch(bdata, d='utf-8'):
    if type(bdata) is not bytes and type(bdata) is not bytearray:
        return bdata
    try:
        return bdata.decode(d)
    except Exception as Error1:
        try:
            if d == 'utf-8':
                return bdata.decode('gbk')
            else:
                return bdata.decode('utf-8')
        except Exception as Error2:
            return bdata.decode(d, errors='ignore')


def create_zip(startdir, file_news):
    z = zipfile.ZipFile(file_news, 'w', zipfile.ZIP_DEFLATED)  # 参数一：文件夹名
    for dirpath, dirnames, filenames in os.walk(startdir):
        fpath = dirpath.replace(startdir, '')  # 这一句很重要，不replace的话，就从根目录开始复制
        fpath = fpath and fpath + os.sep or ''  # 这句话理解我也点郁闷，实现当前文件夹以及包含的所有文件的压缩
        for filename in filenames:
            if os.path.join(dirpath, filename) != file_news:
                z.write(os.path.join(dirpath, filename), fpath + filename)
                os.remove(os.path.join(dirpath, filename))
            pass
    z.close()


def extract_zip(zip_file_path, extract_path):
    if not os.path.exists(extract_path):
        os.makedirs(extract_path)
    zip_file = zipfile.ZipFile(zip_file_path)
    for names in zip_file.namelist():
        zip_file.extract(names, extract_path)
    zip_file.close()


def run_cmd(cmd, logger=None, wait=True, exe_path=None, shell=True):
    error = None
    if logger:
        logger.info(cmd)
    if wait:
        process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=shell)
        line = process.stdout.read()
        error = process.stderr.read()
        if logger:
            if line:
                # logger.info("\r\n" + decode_ch(line))
                pass
            if error:
                error = decode_ch(error)
                logger.error("\r\n" + error)
        result = process.returncode
        process.kill()
    else:
        os.system(cmd)
        return None
    if exe_path and error:
        if error.find(exe_path, 0, len(exe_path)) != -1:
            error = error[len(exe_path) + 2:]
    return error


async def aio_run_cmd(cmd, logger=None, exe_path=None, timeout=10.0):
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
    if logger:
        logger.info(decode_ch(cmd))
    process = await asyncio.create_subprocess_shell(cmd, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
    try:
        line = await asyncio.wait_for(process.stdout.read(), timeout)
        error = await asyncio.wait_for(process.stderr.read(), timeout)
        if logger:
            if line:
                logger.info("\r\n" + decode_ch(line))
                pass
            if error:
                error = decode_ch(error)
                logger.error("\r\n" + error)
        result = process.returncode
        process.kill()
    except asyncio.TimeoutError:
        if logger:
            logger.error(cmd + " timeout")
        else:
            print(cmd, "timeout")
        return cmd + " timeout"
    except ProcessLookupError:
        pass
    if logger:
        logger.info(decode_ch(cmd) + " done!")    
    if exe_path and error:
        if error.find(exe_path, 0, len(exe_path)) != -1:
            error = error[len(exe_path) + 2:]
    return error
# 数值转bytes
# 输入
# num 数值
# byte_num 转换出来的字节数
# is_signed 是否带符号位
# is_bigend 是否大端格式
# 输出
# bytearray


def num_to_array(num, byte_num=4, is_bigend=False, is_signed=False):
    result = bytes()
    if num < 0:
        is_signed = True

    if is_signed:
        if is_bigend:
            result = num.to_bytes(8, byteorder='big', signed=True)
        else:
            result = num.to_bytes(8, byteorder='little', signed=True)
    else:
        if is_bigend:
            result = num.to_bytes(8, byteorder='big', signed=False)
        else:
            result = num.to_bytes(8, byteorder='little', signed=False)

    if is_bigend:
        return bytearray(result[(8-byte_num):8])
    else:
        return bytearray(result[0:byte_num])


# bytes或者string转数值
# 输入
# cls bytes或者string
# is_signed 是否带符号位
# is_bigend 是否大端格式
# 输出
# num
def array_to_num(cls, is_bigend=False, is_signed=False):

    if type(cls) == str:
        cls = bytearray.fromhex(cls)
    if (type(cls) != bytes) & (type(cls) != bytearray):
        return 0

    result = 0
    if is_signed:
        if is_bigend:
            result = int.from_bytes(cls, byteorder='big', signed=True)
        else:
            result = int.from_bytes(cls, byteorder='little', signed=True)
    else:
        if is_bigend:
            result = int.from_bytes(cls, byteorder='big', signed=False)
        else:
            result = int.from_bytes(cls, byteorder='little', signed=False)
    return result


# 列表，元组，hex字符串，bytes转bytearray
def all_to_array(cls, is_hex=True):
    if type(cls) == bytearray:
        return cls
    if type(cls) == list:
        return bytearray(cls)
    if type(cls) == str:
        if is_hex:
            return bytearray.fromhex(cls)
        else:
            try:
                return bytearray(cls, "gbk")
            except Exception as identifier:
                return bytearray(cls, "utf-8", errors='ignore')

    if type(cls) == bytes:
        return bytearray(cls)
    if type(cls) == tuple:
        return bytearray(list(cls))
    return bytearray()


def async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def all_to_hexstring(cls, cut='', is_hex=True):
    result1 = all_to_array(cls, is_hex)
    result2 = ('').join('{:02x}{}'.format(b, cut) for b in result1)
    return result2


def get_name_from_path(path):
    base_name = os.path.basename(str(path))
    app_name, app_suffix = os.path.splitext(base_name)
    return app_name


def make_msg(src=None, dest=None, event=None, value=None, rsp=None):
    msg = {
        'src': src,
        'dest': dest,
        'event': event,
        'value': value,
        'rsp': rsp
    }
    return msg


def make_boardcast_msg(src, event=None, value=None, rsp=None):
    msg = {
        'src': src,
        'dest': None,
        'event': event,
        'value': value,
        'rsp': rsp
    }
    return msg


def crc16(bdata, crc16_root, crc16_start, reverse=False):
    mCrc = ctypes.c_ushort(crc16_start)
    mRoot = ctypes.c_ushort(crc16_root)
    data_len = len(bdata)
    if reverse:
        mRoot.value = 0
        for i in range(0, 16):
            if (crc16_root & (1 << (15-i)) != 0):
                mRoot.value = mRoot.value + (1 << i)
        for j in range(0, data_len):
            temp_data = bdata[j]
            for i in range(0, 8):
                if ((mCrc.value & 0x0001) != 0):
                    mCrc.value = (mCrc.value >> 1) ^ mRoot.value
                else:
                    mCrc.value = mCrc.value >> 1
                if (temp_data & (1 << i) != 0):
                    mCrc.value = mCrc.value ^ mRoot.value

    else:
        for j in range(0, data_len):
            temp_data = bdata[j]
            for i in range(8, 0, -1):
                if (mCrc.value & 0x8000 != 0):
                    mCrc.value = (mCrc.value << 1) ^ mRoot.value
                else:
                    mCrc.value = mCrc.value << 1
                if (temp_data & (1 << (i - 1)) != 0):
                    mCrc.value = mCrc.value ^ mRoot.value

    return mCrc.value


def sum_check(bdata):
    result = 0
    for i in bdata:
        result += i
    return result


def xor_check(bdata, start=0x00):
    result = start
    for i in bdata:
        result = result ^ i
    return result


# 公式计算两点间距离（m）
def geodistance(lng1, lat1, lng2, lat2):
    lng1, lat1, lng2, lat2 = map(radians, [float(lng1), float(lat1), float(lng2), float(lat2)])     # 经纬度转换成弧度
    dlon = abs(lng2-lng1)
    dlat = abs(lat2-lat1)
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    distance = 2*asin(sqrt(a))*6371*1000            # 地球平均半径，6371km
    distance = round(distance/1000, 3)
    return distance


# IMEI 校验
def check_imei(imei):
    if len(imei) != 15:
        return False
    imei = decode_ch(imei)
    if not imei.isdecimal():
        return False
    data = imei[0:-1]
    count = 0
    Sum = 0

    for item in data:
        count = count + 1
        if count % 2 == 0:
            tmp = int(item)
            tmp = 2 * tmp
            tmp = tmp % 10 + tmp // 10
        else:
            tmp = int(item)
        Sum = Sum + tmp

    crc = Sum % 10
    if crc != 0:
        crc = 10 - crc
    if crc == int(imei[-1]):
        return True
    else:
        return False


def find_new_dir(path_dir, dirs):
    new_dir = None
    for t in dirs:
        if not new_dir:
            new_dir = Path(path_dir, t)
        else:
            stat_x = os.stat(new_dir)
            stat_y = os.stat(Path(path_dir, t))
            if stat_x.st_ctime < stat_y.st_ctime:
                new_dir = Path(path_dir, t)
    return new_dir


def g_debug(info):
    global g_debug_mode
    if g_debug_mode:
        print(info)


def set_g_debug_mode(new_mode):
    global g_debug_mode
    g_debug_mode = new_mode


if __name__ == '__main__':
    [print(x) for x in range(0, 7)]
    [print(x) for x in range(8, 10)]
